////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Particle Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

float ui_ExposureBias < string UIName = "Exposure Bias"; string UIGroup = "Tonemapping"; string UIWidget = "Slider"; float UIMin = 0.0; float UIMax = 5.0; > = 0.65;

#include "MODULES/global.h"
#include "MODULES/tonemap_variants.fx"

//==============================================================================================================
// globals and shared variables
//==============================================================================================================

shared float4x4 cmWorldView : REG_cmWorldView; //WORLDVIEW
shared float4x4 cmWorldMat : REG_cmWorldMat; //local to world matrix
shared float3 cvLocalEyePos : REG_cvLocalEyePos; //LOCALEYEPOS;
shared float4 cvTextureOffset : REG_cvTextureOffset; //TEXTUREANIMOFFSET;
shared float4 cvLocalCenter : REG_cvLocalCenter; //LOCALCENTER;
shared float4 cvBaseAlphaRef : REG_cvBaseAlphaRef; //BASEALPHAREF; 
shared float4 cfSplitScreenUVScale : REG_cfSplitScreenUVScale;
shared float cfMipMapBias : REG_cfMipMapBias;

float Time;

// Light Arrays
float4 cvLightColour;
float4 cvLightPosition;

// Artist-controlled Ambient-Colour
float3 ambient_colour = float3(0.92, 1.0, 1.1);

// Debug- and Effect-Constants
float magnitude_debug = 0.95;
static const float FuzzWidth = 0.125f;

// Constants for Bloom and Particle-Effects
static const float BloomThreshold = 0.575f;
static const float BloomIntensity = 1.350f;
static const float ParticleBrightness = 0.75;
static const float ParticleShininess = 12.0f;
static const float3 MaterialSpecular = float3(0.4, 0.4, 0.5);

// Constants for Partciclesize and Shinyness
static const float MaxParticleSize = 0.15f;
static const float MaterialShininess = 10;

// Tonemapping-Constants
// 1 = Reinhard, 2 = ACES2, 3 = ACES Legacy, 4 = Uncharted2, 5 = Unreal, 6 = Lottes, 7 = Gran Turismo,
// 8 = Narkowicz 2015, 9 = AgX, 10 = FilmicALU, 11 = NFS Heat Style, 12 = Reinhard2, 13 = Uchimura 2017
#define TONEMAP_VARIANT 11
static const float EXPOSURE_BIAS = 1.15;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Samplers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

DECLARE_TEXTURE(DIFFUSE_TEXTURE)
sampler DIFFUSE_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(DIFFUSE_TEXTURE) // needed for the PC
    AddressU = CLAMP;
    AddressV = CLAMP;
    MIPFILTER = NONE;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(MISCMAP1_TEXTURE)
sampler MISCMAP1_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(MISCMAP1_TEXTURE) // needed for the PC
    AddressU = CLAMP;
    AddressV = CLAMP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(OPACITY_TEXTURE)
sampler OPACITY_SAMPLER = sampler_state // rain alpha texture
{
    ASSIGN_TEXTURE(OPACITY_TEXTURE)
    AddressU = CLAMP;
    AddressV = CLAMP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

DECLARE_TEXTURE(NORMAL_TEXTURE)
sampler NORMALMAP_SAMPLER = sampler_state
{
    ASSIGN_TEXTURE(NORMAL_TEXTURE)
    AddressU = WRAP;
    AddressV = WRAP;
    MIPFILTER = LINEAR;
    MINFILTER = LINEAR;
    MAGFILTER = LINEAR;
};

sampler HEIGHTMAP_SAMPLER;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Structs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct VS_INPUT
{
    float4 position : POSITION;
    float4 color : COLOR;
    float4 tex : TEXCOORD;
    float4 size : TEXCOORD1;
    int4 light_index : BLENDINDICES;
};

struct VtoP_NormalMapped
{
    float4 position : POSITION;
    float4 color : COLOR0;
    float3 lightColor : COLOR1;
    float4 tex : TEXCOORD0;
    float4 tex1 : TEXCOORD1;
    float3 to_light_tan : TEXCOORD2;
	float3 half_angle_tan	: TEXCOORD4;
    float4 position2 : TEXCOORD3;
};

struct PS_OUTPUT
{
    float4 color : COLOR0;
};

struct VtoP
{
    float4 position : POSITION;
    float4 color : COLOR0;
    float4 tex : TEXCOORD0;
    float4 tex1 : TEXCOORD1;
};

struct VtoP_Depth
{
    float4 position : POSITION;
    float dist : COLOR0;
};

struct VS_INPUT_FLARES
{
    float4 position : POSITION;
    float4 color : COLOR;
    float4 tex : TEXCOORD;
    float4 size : TEXCOORD1;
};

struct VtoP_FLARES
{
    float4 position : POSITION;
    float4 color : COLOR0;
    float4 tex : TEXCOORD0;
    float4 tex1 : TEXCOORD1;
};

struct VtoP_SFLARES
{
    float4 position : POSITION;
    float4 color : COLOR0;
    float4 tex : TEXCOORD0;
};

struct VtoP_RAIN
{
    float4 position : POSITION;
    float4 tex : TEXCOORD0;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper-Functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Bloom-Function
float3 ApplyBloom(float3 colour)
{
    float lum = dot(colour, float3(0.2126, 0.7152, 0.0722));
    float bloom = saturate((lum - BloomThreshold) / max(1e-5, (1.0 - BloomThreshold))) * BloomIntensity;
    return colour + bloom;
}

// Tonemap-Function
float3 ApplyTonemap(float3 hdrColor)
{
    float3 color = hdrColor * EXPOSURE_BIAS;
    
#if TONEMAP_VARIANT == 1
    return Tonemap_Reinhard(color);
#elif TONEMAP_VARIANT == 2
    return Tonemap_ACES2(color);
#elif TONEMAP_VARIANT == 3
    return Tonemap_ACESLegacy(color);
#elif TONEMAP_VARIANT == 4
    return Tonemap_Uncharted2(color);
#elif TONEMAP_VARIANT == 5
    return Tonemap_Unreal(color);
#elif TONEMAP_VARIANT == 6
    return Tonemap_Lottes(color);
#elif TONEMAP_VARIANT == 7
    return Tonemap_GranTurismo(color);
#elif TONEMAP_VARIANT == 8
    return Tonemap_Narkowicz(color);
#elif TONEMAP_VARIANT == 9
    return Tonemap_AgX(color);
#elif TONEMAP_VARIANT == 10
    return Tonemap_FilmicALU(color);
#elif TONEMAP_VARIANT == 11
    return Tonemap_NFSHeatStyle(color);
#elif TONEMAP_VARIANT == 12
    return Tonemap_Reinhard2(color);
#elif TONEMAP_VARIANT == 13
    return Tonemap_Uchimura(color);
#endif
}

// Colorcorrection-Function
float3 ApplyColorGradeParticles(float3 color)
{
    float3 tint = float3(1.25, 1.10, 1.35); // slightly blueish
    color *= tint;

    // simple Cross-Processing
    color.r = pow(color.r, 1.1);
    color.g = pow(color.g, 0.95);
    color.b = pow(color.b, 1.05);

    return color;
}

float3 ApplyColorGradeFlares(float3 color)
{
    float3 tint = float3(1.05, 0.75, 0.85); // slightly yellowish
    color *= tint;

    // simple Cross-Processing
    color.r = pow(color.r, 1.1);
    color.g = pow(color.g, 0.95);
    color.b = pow(color.b, 1.05);

    return color;
}

float3 ApplyColorGradeRain(float3 color)
{
    float3 tint = float3(1.30, 1.00, 1.90); // blueish
    color *= tint;
    
    // simple Cross-Processing
    color.r = pow(color.r, 1.1);
    color.g = pow(color.g, 0.95);
    color.b = pow(color.b, 1.05);
    
    return color;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Particle Shader
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

float3x3 BuildRotate(float angle, float3 rotAxis)
{
    float3x3 m;
    // float fSin = sin(angle);
    // float fCos = cos(angle);
    float2 sc;
    sincos(angle, sc.x, sc.y);
    float3 axis = normalize(rotAxis);

    float3 cosAxis = (1.0f - sc.y) * axis;
    float3 sinAxis = sc.x * axis;
    m[0] = cosAxis.x * axis;
    m[1] = cosAxis.y * axis;
    m[2] = cosAxis.z * axis;
    m[0][0] += sc.y;
    m[0][1] += sinAxis.z;
    m[0][2] -= sinAxis.y;
    m[1][0] -= sinAxis.z;
    m[1][1] += sc.y;
    m[1][2] += sinAxis.x;
    m[2][0] += sinAxis.y;
    m[2][1] -= sinAxis.x;
    m[2][2] += sc.y;
    
    return m;
}

VtoP_NormalMapped vertex_shader_particles(const VS_INPUT IN)
{
    VtoP_NormalMapped OUT;
    
	// Offset the vertex by the particle size		
    float3 right = cmWorldView._m00_m10_m20;
    float3 up = cmWorldView._m01_m11_m21;
    float3 facing = cmWorldView._m02_m12_m22;

	// Rotate the up and right around the facing
    float angle = IN.size.z;
    if (angle > 0)
	{
        float3x3 rotation = BuildRotate(angle, facing);
        right = mul(right, rotation);
        up = mul(up, rotation);
    }

	// Add offset from particle midpoint to the outside vertices
    float4 pv = IN.position;
    float3 offset = right * IN.size.x + up * IN.size.y;
    pv.xyz += offset;

	// Cap the screen size of any particle
    float4 worldCornerPos = pv;
    pv = world_position(pv);

    float3 pvn = pv.xyz / pv.w;
    float4 pc = world_position(IN.position);
    float3 pcn = pc.xyz / pc.w;
    float size = distance(pvn.xy, pcn.xy);
    float new_size = min(size, MaxParticleSize);
    float scale = new_size / size;
    pv = lerp(pc, pv, scale);

	// Each particle is affected by one light in the light array
	// Read which one it is:
    int lightIndex = IN.light_index.x;
    float3 lightPos = cvLightPosition.xyz;
		
    float3 worldPos = mul(IN.position, cmWorldMat);
    float3 toLightSource = normalize(lightPos - worldPos);
	
	// Create the matrix which transforms from world space to tangent space
    float3 tangent = right;
    float3 binormal = up;
    float3 normal = -facing;
    float3x3 matTSpace = transpose(float3x3(tangent, binormal, normal));

    OUT.to_light_tan = mul(toLightSource, matTSpace);
	
    float3 toEyeWorld = normalize(cvLocalEyePos - worldPos);
    float3 toEyeTan = mul(toEyeWorld, matTSpace);
	 
	// Calculate the half-angle vector for per-pixel specular (maybe this is overkill for particles)
	OUT.half_angle_tan = (toEyeTan + OUT.to_light_tan) * 0.5;
			
    float3 lightColour = cvLightColour.rgb;
    OUT.lightColor = 1;
					
    OUT.position = OUT.position2 = pv;
    OUT.color = saturate(IN.color * 2);
    // OUT.color.xyz *= ambient_colour;

    OUT.tex = IN.tex + cvTextureOffset;
	
	// Convert from screen space (-1 to 1) to texture coordinate space (0.0 to 1.0)
    float distance = pv.z / pv.w;
    OUT.tex1.x = (0.5 * pv.x / pv.w) + 0.5;
    OUT.tex1.y = (-0.5 * pv.y / pv.w) + 0.5;
    OUT.tex1.y *= cfSplitScreenUVScale[1]; // Split screen adjustment
    OUT.tex1.y += cfSplitScreenUVScale[0]; // Split screen adjustment
	
	// Distance to the pixel in the depth buffer
    OUT.tex1.z = distance;
	
	// Calculate the distance from the particle to the camera in world coordinates
    OUT.tex1.w = length(cvLocalEyePos - IN.position);
	

    return OUT;
}

PS_OUTPUT pixel_shader_particles(const VtoP_NormalMapped IN)
{
    PS_OUTPUT OUT;
    
    float shadow = 1;
    float4 baseColour = tex2D(DIFFUSE_SAMPLER, IN.tex) * IN.color;	
    float depth = tex2D(HEIGHTMAP_SAMPLER, IN.tex1.xy).x;
		
    float zFar = 10000;
    float zNear = 0.5;
    float Q = zFar / (zFar - zNear);
    float zDist = (-Q * zNear / (depth - Q));
		
    float depthBufferDistToParticle = IN.position2.z / IN.position2.w;
    float distanceToParticle = (-Q * zNear / (depthBufferDistToParticle - Q));

    float distanceBetweenParticleAndGround = abs(zDist - distanceToParticle);
    float fuzzz = saturate(distanceBetweenParticleAndGround * 1);

	// calculate the normal map
    float3 normal = tex2D(NORMALMAP_SAMPLER, IN.tex) * 2 - 1;	
    normal = normal * magnitude_debug;

	// Apply diffuse lighting 
    float3 toLight = normalize(IN.to_light_tan);
    float nDotL = saturate(dot(normal, toLight));		 
    float3 diffuseColour = nDotL * IN.lightColor;

	// specular calculations
    float3 half_angle = normalize(IN.half_angle_tan);
    float nDotH = saturate(dot(normal, half_angle));
    float3 specular = MaterialSpecular * pow(nDotH, MaterialShininess);
    
    {
        float3 col = baseColour.rgb;
        col = ApplyTonemap(col);
        col *= saturate(ParticleBrightness);
        col = ApplyColorGradeParticles(col);
        col = ApplyBloom(col);
        baseColour.rgb = col;
    }
    
    OUT.color.rgb = fuzzz * shadow * baseColour * (ambient_colour + diffuseColour + specular);
    OUT.color.a = baseColour.a * shadow * fuzzz;
	
    OUT.color.rgb = CompressColourSpace(OUT.color.rgb);
	
    return OUT;
}

technique particles 
{
    pass p0
    {
        VertexShader = compile vs_3_0 vertex_shader_particles();
        PixelShader  = compile ps_3_0 pixel_shader_particles();
    }
}

///////////////////////////////////////////////////////////////////////////////////////
//
// FLARES
//
///////////////////////////////////////////////////////////////////////////////////////

VtoP_FLARES vertex_shader_flares(const VS_INPUT_FLARES IN)
{
    VtoP_FLARES OUT;
	// Offset the vertex by the particle size		
    float3 right = cmWorldView._m00_m10_m20;
    float3 up = cmWorldView._m01_m11_m21;
    float3 facing = cmWorldView._m02_m12_m22;
    float isTrailFlare = IN.size.w;

    float angle = IN.size.z;
	// Rotate the up and right around the facing
    if (angle > 0)
	{
        float3x3 rotation = BuildRotate(angle, facing);
        right = mul(right, rotation);
        up = mul(up, rotation);
    }
	
    float4 pv = IN.position;
    pv.xyz += right * IN.size.x + up * IN.size.y;

	// Cap the screen size of any particle
    pv = world_position(pv);

    OUT.position = pv;
    OUT.color = IN.color;
    OUT.tex = IN.tex;
    OUT.tex.w = cfMipMapBias; // bias the mipmapping
//	OUT.tex1 = IN.size;


	// Convert from screen space (-1 to 1) to texture coordinate space (0.0 to 1.0)
    float distance = pv.z / pv.w;
    OUT.tex1.x = (0.5 * pv.x / pv.w) + 0.5;
    OUT.tex1.y = (-0.5 * pv.y / pv.w) + 0.5;
    OUT.tex1.z = distance; // Distance to the pixel in the depth buffer
    OUT.tex1.y *= cfSplitScreenUVScale[1]; // Split screen adjustment
    OUT.tex1.y += cfSplitScreenUVScale[0]; // Split screen adjustment
	
	// We need to scale FuzzWidth down if the sprite is near the back of the depth buffer. For instance, FuzzWidth could correspond to 1 cm
	// at the start of the depth buffer, but it could be 100 meters at the end of it
    OUT.tex1.w = saturate(FuzzWidth + distance * (-FuzzWidth));

		
    return OUT;
}



float4 pixel_shader_flares(const VtoP_FLARES IN) : COLOR
{
    float4 diffuse = tex2Dbias(DIFFUSE_SAMPLER, IN.tex);

    float scaled_fuzz_width = IN.tex1.w;
    float depth = tex2D(HEIGHTMAP_SAMPLER, IN.tex1.xy).x;
    float fuzzz = saturate((scaled_fuzz_width * 0.4 - (IN.tex1.z - depth)) / (scaled_fuzz_width));
	
	
    float4 result;
    result = diffuse;
    result *= IN.color; //*cvBaseAlphaRef.y;
	// Apply a tone mapping to fake a HDR
    result.xyz = result.xyz / (1.5 - result.xyz);
	//result.xyz = pow(result.xyz, 1.5)*1.5;
	//result.xyz	= result.x &gt; 0.95 ? 1.0f : 0.0f;
	// cvBaseAlphaRef.x is set to 0 in players views so the fuzzz used and 1 to disable the fuzz
    result.w *= saturate(cvBaseAlphaRef.x + fuzzz);
	
	//result.xyz = fuzzz;//(scaled_fuzz_width - (IN.tex1.z - depth)) / FuzzWidth;
	//result.w = 1;
    result.xyz = CompressColourSpace(result.xyz);
	
    return result;
}

technique flares
{
    pass p0
    {
        VertexShader = compile vs_1_1 vertex_shader_flares();
        PixelShader = compile ps_2_0 pixel_shader_flares();
    }
}

///////////////////////////////////////////////////////////////////////////////////////
//
// STREAK FLARES
//
///////////////////////////////////////////////////////////////////////////////////////




VtoP_SFLARES vertex_shader_streak_flares(const VS_INPUT_FLARES IN)
{
    VtoP_SFLARES OUT;
	// Offset the vertex by the particle size		
    float3 right = cmWorldView._m00_m10_m20;
    float3 up = cmWorldView._m01_m11_m21;
    float3 facing = cmWorldView._m02_m12_m22;
    float doStreakFlare = IN.size.w;
    float4 pv = IN.position;
    float4 vertexColour = IN.color;
    float4 tex = IN.tex;

    if (doStreakFlare > 0)
	{
        float cameraSpeed = cvBaseAlphaRef.y;
        float nosAmount = cvBaseAlphaRef.w;
		
		// Add some speed for NOS
		//
        cameraSpeed += nosAmount * 20;
		
		//
		// Flare is stretch to create a flare trail for Sense of Speed
		//
        float4 currFramePos = mul(IN.position, cmWorldViewProj);

		// Redice length if camera veleocity is off centre in the x direction
        cameraSpeed = lerp(cameraSpeed, 0, saturate(abs(cvBaseAlphaRef.x) / 0.05));

        float4 prevFramePos = currFramePos;
        prevFramePos.zw += 3 + saturate(cameraSpeed / 140) * 45; // Stretch flare back
        currFramePos += normalize(currFramePos - prevFramePos) * 10; // Stretch flare forward

		// Build the poly 		
        float3 prevFrameDir = prevFramePos.xyz / prevFramePos.w - currFramePos.xyz / currFramePos.w;
        float offsetWidth = min(abs(IN.size.x), abs(IN.size.y));
        offsetWidth *= 0.2;
        float4 currFrameOffset = mul(IN.position + float4(offsetWidth, offsetWidth, offsetWidth, 0), cmWorldViewProj);
        float currFrameSize = distance(currFrameOffset.xy, currFramePos.xy);
        float3 prevFrameDirTanget = normalize(prevFrameDir);
 		// Rotate by 90 degrees to get tangent
        prevFrameDirTanget.xy = prevFrameDirTanget.yx;
        prevFrameDirTanget.x = -prevFrameDirTanget.x;
        prevFrameDirTanget *= currFrameSize;

		// Push the tex coord futher along the streak for longer streaks 		
        float texUlength = saturate(length(prevFrameDir.xyz) / 0.1); // 0.3
        tex.x = IN.size.x > 0 ? 1 / 256 : texUlength;
        tex.y = IN.size.y > 0 ? cvBaseAlphaRef.z : cvBaseAlphaRef.z + 15.0f / 128.0f; // The v texcoord is calc in on the CPU
        tex.z = IN.size.x > 0 ? 0 : 1;
		
        pv = IN.size.x > 0 ? currFramePos : prevFramePos;

		// Adjust brightness for lower/higher speeds
        vertexColour *= saturate(cameraSpeed / 95);
		
		// Add the offset to create width for the streak along the tangent
		//
        pv.xy += sign(IN.size.y) * prevFrameDirTanget;
		//pv.z = 1;
    }
	else
	{
		// Push offscreen to clip
        pv = 1000;
    }
	
    OUT.position = pv;
    OUT.color = vertexColour;
    OUT.tex = tex;
    OUT.tex.w = -5; // bias the mipmapping
		
    return OUT;
}


float4 pixel_shader_streak_flares(const VtoP_SFLARES IN) : COLOR
{
    float4 result = tex2Dbias(DIFFUSE_SAMPLER, IN.tex);;
    result *= IN.color;
	
    return result;
}

technique streak_flares
{
    pass p0
    {
        VertexShader = compile vs_1_1 vertex_shader_streak_flares();
        PixelShader = compile ps_2_0 pixel_shader_streak_flares();
    }
}


///////////////////////////////////////////////////////////////////////////////////////
//
// Onscreen rain particle effect
//

VtoP_RAIN vertex_shader_passthru(const VS_INPUT IN)
{
    VtoP_RAIN OUT;
    OUT.position = screen_position(IN.position);
    OUT.position.w = 1.0f;
    OUT.tex = IN.tex;

    return OUT;
}

float4 pixel_shader_onscreen_distort(const VtoP_RAIN IN) : COLOR0
{
    float4 distortion = tex2D(DIFFUSE_SAMPLER, IN.tex);
    float2 offset = distortion.gb * cvLocalCenter.ba + cvLocalCenter.rg;
    float4 background = tex2D(MISCMAP1_SAMPLER, offset);
	
	// The opacity map has four different raindrop texture tiled horizontally.  The
	// offset into this texure is stored in cvBaseAlphaRef.y
	//
    offset = IN.tex;
    offset.x = cvBaseAlphaRef.y + IN.tex.x * 0.25;
    float4 opacity = tex2D(OPACITY_SAMPLER, offset);
	
    float4 result;
    result = background * opacity.y;
    result.w = opacity.r * cvBaseAlphaRef.x;

	//result = opacity;
	
    return result;
}

technique onscreen_distort
{
    pass p0
    {
        VertexShader = compile vs_1_1 vertex_shader_passthru();
        PixelShader  = compile ps_2_0 pixel_shader_onscreen_distort();
    }
}